# -- coding: utf-8 --
from mip import Model, INTEGER, CONTINUOUS, BINARY, minimize, xsum, LinExpr
import pandas as pd
from enum import Enum


# 铁路线路
class RailwayLine:
    def __init__(self, list):  # list: ['北京', '天津', '济南', '南京', '上海']
        self.StationList = []
        self.SectionList = []
        self.PrevSection = {}
        self.NextSection = {}
        for name in list:  # name: '北京'->'天津'->'济南'->'南京'->'上海'
            if len(self.StationList) == 0:  # 第一次len(self.StationList)是等于0的，后面都大于0
                self.StationList.append(name)  # 将name—>北京添加进StationList
            # 返回for name in list，此时name变成天津
            else:
                last = self.StationList[len(self.StationList) - 1]  # last:北京
                station = name  # name天津赋给station—name济南赋给station
                section = Section(last, station)
                self.StationList.append(station)  # StationList变为：北京、天津
                self.SectionList.append(section)
                self.PrevSection[station] = section
                self.NextSection[last] = section

    # 打印
    def Print(self):
        for station in self.StationList:
            if station == self.StationList[len(self.StationList) - 1]:
                print(station)
            else:
                print(station, '-->', end="")

    # 通过车站名称获取区间
    def GetSec(self, sta1, sta2):
        for sec in self.SectionList:
            if sec.start_station == sta1 and sec.end_station == sta2:
                return sec

    # 获取除去首末站的其它车站
    def GetInterStation(self):
        for station in self.StationList:
            if not (station == self.StationList[0] or station == self.StationList[len(self.StationList) - 1]):
                yield station


# 区间
class Section:
    def __init__(self, station1, station2):
        self.start_station = station1
        self.end_station = station2


# 备选集
class AlternativeSet:
    def __init__(self):
        self.LineList = []


# 方案线
class TrainLine:
    def __init__(self, name):
        self.Name = name
        self.AlongStation = []
        self.AlongList = []
        self.AlongSection = []
        self.CoveredOD = []
        self.Mode = TranMode.Mode_Zl
        self.Capacity = 0

    # 增加停站
    def AddStop(self, station, isStop):
        self.AlongList.append(Stop(station, isStop))
        # 获取区间运行时间

    # 增加索引
    def BuildIndex(self, net):
        # 途径车站索引
        for index in self.AlongList:
            self.AlongStation.append(index.Station)
        # 途径区间索引
        for index in range(len(self.AlongList) - 1):
            self.AlongSection.append(net.GetSec(self.AlongList[index].Station, self.AlongList[index + 1].Station))
        # 覆盖OD索引
        for index1 in range(len(self.AlongList)):
            if self.AlongList[index1].Stop:
                sta1 = self.AlongList[index1].Station
                for index2 in range(index1 + 1, len(self.AlongList)):
                    if self.AlongList[index2].Stop:
                        self.CoveredOD.append(Section(sta1, self.AlongList[index2].Station))

    def Passed(self, section):
        return self.AlongSection.count(section)

    def Covered(self, section):
        for od in self.CoveredOD:
            if od.start_station == section.start_station and od.end_station == section.end_station:
                return True
        return False

    def Od_Section_Overlapped(self, od, section):
        od_start_index = self.AlongStation.index(od.start_station)
        od_end_index = self.AlongStation.index(od.end_station)
        section_start_index = self.AlongStation.index(section.start_station)
        section_end_index = self.AlongStation.index(section.end_station)
        return od_start_index <= section_start_index and od_end_index >= section_end_index


# 列车停站
class Stop:
    def __init__(self, station, stop):
        self.Station = station
        self.Stop = stop

    def IsPass(self):
        return self.Stop


class CargoDemand:
    def __init__(self, od, M_type, num):
        self.od = od
        self.ProdType = M_type
        self.Num = num


class TranMode(Enum):
    Mode_Zl = 1  # 货运专列运输模式
    Mode_Qrc = 2  # 高铁确认车运输模式
    Mode_Yl = 3  # 客运动车组预留车厢运输模式
    Mode_Sd = 4  # 客运动车组捎带运输模式


class ProdType(Enum):
    Within_One_Day = 1  # 当日达
    Next_Morning = 2  # 次晨达
    Next_Day = 3  # 次日达
    After_Tomorrow = 4  # 隔日达


# 生成小案例（改动）
def glb_generate_test_world():
    net = RailwayLine(['北京', '天津', '济南', '南京', '上海'])  # 线路上的停站

    # 下行
    alterSet = AlternativeSet()
    line1 = TrainLine('Line1')
    line1.AddStop('北京', True)
    line1.AddStop('天津', True)
    line1.AddStop('济南', True)
    line1.AddStop('南京', True)
    line1.AddStop('上海', True)
    line1.BuildIndex(net)

    line2 = TrainLine('Line2')
    line2.AddStop('北京', True)
    line2.AddStop('天津', True)
    line2.BuildIndex(net)

    line3 = TrainLine('Line3')
    line3.AddStop('北京', True)
    line3.AddStop('天津', False)
    line3.AddStop('济南', True)
    line3.BuildIndex(net)

    line4 = TrainLine('Line4')
    line4.AddStop('北京', True)
    line4.AddStop('天津', False)
    line4.AddStop('济南', False)
    line4.AddStop('南京', True)
    line4.BuildIndex(net)

    line5 = TrainLine('Line5')
    line5.AddStop('北京', True)
    line5.AddStop('天津', False)
    line5.AddStop('济南', False)
    line5.AddStop('南京', False)
    line5.AddStop('上海', True)
    line5.BuildIndex(net)

    line6 = TrainLine('Line6')
    line6.AddStop('北京', True)
    line6.AddStop('天津', False)
    line6.AddStop('济南', False)
    line6.AddStop('南京', False)
    line6.AddStop('上海', True)
    line6.BuildIndex(net)

    line7 = TrainLine('Line7')
    line7.AddStop('北京', True)
    line7.AddStop('天津', False)
    line7.AddStop('济南', False)
    line7.AddStop('南京', True)
    line7.AddStop('上海', True)
    line7.BuildIndex(net)

    line8 = TrainLine('Line8')
    line8.AddStop('北京', True)
    line8.AddStop('天津', False)
    line8.AddStop('济南', True)
    line8.AddStop('南京', False)
    line8.AddStop('上海', True)
    line8.BuildIndex(net)

    line9 = TrainLine('Line9')
    line9.AddStop('北京', True)
    line9.AddStop('天津', False)
    line9.AddStop('济南', True)
    line9.AddStop('南京', True)
    line9.AddStop('上海', True)
    line9.BuildIndex(net)

    alterSet.LineList.append(line1)
    alterSet.LineList.append(line2)
    alterSet.LineList.append(line3)
    alterSet.LineList.append(line4)
    alterSet.LineList.append(line5)
    alterSet.LineList.append(line6)
    alterSet.LineList.append(line7)
    alterSet.LineList.append(line8)
    alterSet.LineList.append(line9)

    # 参数
    params.Capacity = {net.GetSec('北京', '天津'): 3,  # 区间能力约束
                net.GetSec('天津', '济南'): 5,
                net.GetSec('济南', '南京'): 7,
                net.GetSec('南京', '上海'): 3, }

    params.Demand = {
        Section('北京', '南京'): 2,  # 服务频率约束
        Section('北京', '济南'): 1
    }  # 服务频率应该是求解的一个目标吧

    params.CargoDemand = [
        CargoDemand(Section('北京', '南京'), ProdType.Next_Day, 20),
        CargoDemand(Section('北京', '济南'), ProdType.Within_One_Day, 10)
    ]

    return net, alterSet, params


# 求解问题
def glb_solve_mip(net, alterSet, params):
    '''
    M分别表示四种高铁快递运输模式
        m=1表示货运专列运输模式， m=2表示高铁确认车运输模式， m=3表示客运动车组预留车厢运输模式， m=4表示客运动车组捎带运输模式
    N分别表示四种高铁快递产品
        n=1表示当日达， n=2表示次晨达， n=3表示次日达, n=4表示隔日达）
    '''
    # # 列车装载能力（4种模式，分别为专列、确认车、预留车厢、捎带）
    # Q = [120, 52.3, 8, 5.21]  # 列车装载能力
    # M = 4  # 4种运输模式
    # N = 4  # 4种运输产品
    # η = 0.8
    # df = pd.read_csv(r'E:\研究生论文\小论文\小论文代码\主要节点货运需求.txt', sep=" ")
    # df = pd.read_csv(r'E:\研究生论文\小论文\小论文代码\主要节点间距离.txt', sep=" ")
    # df = pd.read_csv(r'E:\研究生论文\小论文\小论文代码\各类列车费用表.txt', sep=" ")
    # C[m, 1]
    # x = {}  # 初始化决策变量
    # model = Model()
    # frequency_var = {}
    #
    # for m in range(M):
    #     for n in range(N):
    #         x = {(m, n, w): model.add_var(var_type=BINARY, for m in range(M) for n in range(N) for l in
    #              range(alterSet))}
    #
    #         # 目标：成本最小（固定成本+可变成本）
    #         model.objective = minimize(xsum(
    #             C[m][1] * x[m][n][w] * f[m][l] + C[m][2] * d[i][j] * x[m][n][w] * f[m][l] for m in range(M) for n in
    #             range(N) for l in range(alterSet)))
    #
    #         # 约束1：需求满足约束
    #         model += (xsum(x[m][n][w] * q[w][m][l]) >= xsum(Q[m][w]) for m in range(M) for n in range(N) for l in
    #                   range(alterSet))
    #
    #         # 约束2：产品供给特征约束
    #         model += xsum(x[m, n, w]) <= 1 and x[2, 1, w] == 0
    #         model += xsum(η * q[w][m][l] * f[m][l] <= Q[m][w][l])
    #
    #         # 约束3：区间通过能力约束
    #         for sec in net.SectionList:  # 遍历所有的区间
    #             model += xsum(x[i][j][l] * f[m][l] for line in alterSet.LineList) <= params.Section_Capacity[sec]
    #
    #     # 约束4：货物断面流量约束
    #     # 约束5：运量匹配约束。
    #     # 约束6：只有模式匹配才有运量，否则运量为0

    model = Model()
    frequency_var = {}
    q = {}

    # 初始化变量
    for line in alterSet.LineList:
        frequency_var[line] = model.add_var(lb=0, ub=999, var_type=INTEGER, name=line.Name)
        q[line] = {(m, od): model.add_var(var_type=CONTINUOUS) for m in ProdType for od in
                   line.Covered}  # 变量q - 每条方案线运送 不同OD, 不同类型服务 的数量

    # 约束1 ：需求满足约束
    for d in params.CargoDemand:
        model += xsum(frequency_var[line] * q[line][d.ProdType, d.od] for line in alterSet.LineList) >= d.Num

    # 约束2：产品供给特征约束
    # （1）高铁确认车不满足当日达货运产品的运输需求
    for line in alterSet.LineList:
        if line.Mode == TranMode.Mode_Qrc:
            for od in line.Covered:
                model += q[line][ProdType.Within_One_Day, od] == 0

    # （2）满载率 没看懂

    # 约束3：区间能力约束
    for sec in net.SectionList:  # 遍历所有的区间
        model += xsum(line.Passed(sec) * frequency_var[line] for line in alterSet.LineList) <= params.Section_Capacity[
            sec]

    # 约束4：货物断面流量约束
    for line in alterSet.LineList:
        for sec in line.AlongSection:
            model += xsum(q[line][m, od] * line.Od_Section_Overlapped(od, sec) for m in ProdType for od in
                          line.Covered) <= line.Capacity

    # 约束Y：OD服务频率约束
    # for od in params.Demand:
    #     model += xsum(line.Covered(od) * frequency_var[line] for line in alterSet.LineList) >= params.Demand[od]

    # 目标：成本最小
    model.objective = minimize(params.Fixed_Cost * xsum(frequency_var[line] for line in alterSet.LineList))

    # 求解
    model.write("model.lp")
    model.max_mip_gap = 0.01
    model.optimize(max_seconds=300)

    # 输出
    if model.num_solutions:
        for line in alterSet.LineList:
            print('{0}:{1}'.format(line.Name, frequency_var[line].x))
        print('目标函数值:', format(model.objective_value))


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    net, alterSet, params = glb_generate_test_world()  # 线路上的停站
    net.Print()
    glb_solve_mip(net, alterSet, params)
